# encoding=utf-8
# 全局公共方法
# BingoLee 2024-04-19
import os
import time
import json
import traceback
import threading
import requests

from utils.kit_profile import Profile


class Log:
    """
    日志记录类
    """
    debug_switch = False
    log_file = os.path.join(Profile.logs_dir, f"log-{time.strftime('%Y%m%d', time.localtime())}.log")

    @classmethod
    def debug(cls, info: str) -> None:
        """
        记录DEBUG日志信息，当debug_switch为True时才有效
        info: 日志文本内容
        """
        if not cls.debug_switch:
            return
        file, line, func = traceback.extract_stack()[-2][:3]
        cls._write('DEBUG', file, func, line, info)

    @classmethod
    def warn(cls, info: str) -> None:
        """
        记录WARNNING级别日志信息
        info: 日志文本内容
        """
        file, line, func = traceback.extract_stack()[-2][:3]
        cls._write('WARN', file, func, line, info)

    @classmethod
    def info(cls, info: str) -> None:
        """
        记录INFO级别日志信息
        info: 日志文本内容
        """
        file, line, func = traceback.extract_stack()[-2][:3]
        cls._write('INFO', file, func, line, info)

    @classmethod
    def error(cls, info: str) -> None:
        """
        记录ERROR级别日志信息
        info: 日志文本内容
        """
        file, line, func = traceback.extract_stack()[-2][:3]
        cls._write('ERROR', file, func, line, info)

    @classmethod
    def _write(cls, level, file, func, line, info) -> None:
        line = (f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())} | {level} | "
                f"{os.path.basename(file)}:{func}:{line} | {info}\n")
        with open(cls.log_file, 'a+') as f:
            f.write(line)


class Function:
    """
    公共方法类
    """

    @staticmethod
    def create_thread(func, args=()) -> threading.Thread:
        """
        创建一个daemon线程
        func: 线程处理函数
        args: 线程处理函数的参数元祖
        """
        th = threading.Thread(target=func, args=args)
        th.daemon = True
        th.start()
        return th

    @classmethod
    def trigger_timer(cls, func, timeout):
        """
        定时器触发器
        func: 定时器执行的函数
        timeout: 周期描述
        """
        def run():
            while True:
                try:
                    func()
                except Exception as e:
                    Log.error(f'timer {func.__name__} exception: {str(e)}')
                time.sleep(timeout)

        cls.create_thread(run)

    @staticmethod
    def json_read(path: str) -> tuple:
        """
        读取Json配置文件内容
        path: Json配置文件路径
        """
        for _ in range(2):
            try:
                with open(path, 'r', encoding='utf-8') as f:
                    return json.load(f), None
            except Exception as e:
                Log.error(f"json read failed: {str(e)}")
                if 'Permission denied:' in str(e):    # 读写冲突时，重新读一次
                    time.sleep(0.1)
                    continue
                return None, str(e)
        return None, "permission denied for read"

    @staticmethod
    def json_write(path: str, data: dict) -> tuple:
        """
        覆盖写Json文件内容
        path: Json配置文件路径
        data: 要写入的数据字典
        """
        try:
            with open(path, 'w+', encoding='utf-8') as f:
                f.write(json.dumps(data, indent=4, ensure_ascii=False))
        except Exception as e:
            Log.error(f"json write failed: {str(e)}")
            return False, str(e)
        return True, None


class UrlDownloader(object):
    """
    通过URL下载文件
    """

    def __init__(self, url: str, dst_dir: str = './', timeout: int = 0):
        self.url = url
        self.dst_dir = dst_dir
        self.timeout = timeout
        self.start_time = 0
        self.total_size = -1
        self.wrote_size = 0
        self.thread = None
        self.start()

    def start(self) -> None:
        """ 开启线程异步下载 """
        def download(args=None):
            response = requests.get(self.url, stream=True)
            block_size = 102400
            self.total_size = int(response.headers.get('content-length', 0))
            dst = os.path.join(self.dst_dir, self.url.split('/')[-1])

            with open(dst, 'wb+') as f:
                for chunk in response.iter_content(chunk_size=block_size):
                    if chunk:
                        if self.is_timeout():
                            return
                        f.write(chunk)
                        self.wrote_size += len(chunk)

        self.start_time = time.time()
        self.thread = Function.create_thread(download)

    def get_progress(self) -> str:
        """ 获取下载进度信息 """
        if self.wrote_size == 0:
            return '0%'
        percent = str(self.wrote_size / self.total_size * 100)
        percent = '.'.join((percent.split('.')[0], percent.split('.')[1][:2]))
        size = f"{round(self.wrote_size / 1048576, 1)}MB/{round(self.total_size / 1048576, 1)}MB"
        cost = time.time() - self.start_time
        cost_str = f"{int(cost / 3600):0>2}:{int(cost % 3600 / 60):0>2}:{int(cost % 60):0>2}"
        avg = round(self.wrote_size / cost / 1048576, 2)
        eta = (self.total_size - self.wrote_size) / (self.wrote_size / cost)
        eta_str = f"{int(eta / 3600):0>2}:{int(eta % 3600 / 60):0>2}:{int(eta % 60):0>2}"
        return f"{percent}%, {size}, cost: {cost_str}, AVG: {avg}MB/s, ETA: {eta_str}"

    def is_downloading(self) -> bool:
        """ 判断是否在下载中 """
        if self.is_finished():
            return False
        if self.is_timeout():
            return False
        return self.thread.is_alive()

    def is_timeout(self) -> bool:
        """ 判断是否超时 """
        return time.time() - self.start_time > self.timeout

    def is_finished(self) -> bool:
        """ 是否下载完成 """
        return self.wrote_size == self.total_size
